package sketch.ounit.fuzz;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
//import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
//import java.util.Set;

import randoop.ExecutableSequence;
import randoop.ExecutionOutcome;
import randoop.NormalExecution;
//import randoop.util.Randomness;
//import randoop.util.Reflection;
import sketch.specs.RandomSequenceCreator;
import sketch.util.Checker;

public class NewObjectFuzzer {
	
	public static int time_limit = 0;
	public static void setTimeLimit(int time) {
		Checker.checkTrue(time > 0, "The time limit: " + time  + " should > 0");
		time_limit = time;
	}
	
	/**
	 * Data structure for keeping the history state
	 * */
	private static Map<Class<?>, Integer> clazz_time_limit = new LinkedHashMap<Class<?>, Integer>();
	private static Map<Class<?>, List<ExecutableSequence>> clazz_sequences = new LinkedHashMap<Class<?>, List<ExecutableSequence>>();
	private static void update_clazz_history(Class<?> clazz, Collection<ExecutableSequence> collection) {
		int accumulated_time = 0;
		if(clazz_time_limit.containsKey(clazz)) {
			accumulated_time = clazz_time_limit.get(clazz);
		}
		clazz_time_limit.put(clazz, accumulated_time + time_limit);
		//add to the sequence pool
		if(!clazz_sequences.containsKey(clazz)) {
			clazz_sequences.put(clazz, new ArrayList<ExecutableSequence>());
		}
		clazz_sequences.get(clazz).addAll(collection);
	}
	
	private static Map<Class<?>, Integer> returnIndex = new LinkedHashMap<Class<?>, Integer>();
	private static ExecutableSequence nextSequence(Class<?> mainClass) {
		Checker.checkTrue(returnIndex.containsKey(mainClass), "The return index must contain class: " + mainClass);
		int index = returnIndex.get(mainClass);
		//System.out.println(" the index is: " + index);
		returnIndex.put(mainClass, index + 1);
		current_sequence = clazz_sequences.get(mainClass).get(index);
		return current_sequence;
	}
	
	private static<T>  void init(Class<T> mainClass, Class<?>...classes) {
		if(!clazz_time_limit.containsKey(mainClass) || clazz_time_limit.get(mainClass) < time_limit) {
			RandomSequenceCreator seq_creator = new RandomSequenceCreator(mainClass, classes);
			List<ExecutableSequence> allSequences = seq_creator.createAllSequence(time_limit);
			update_clazz_history(mainClass, allSequences);
		}
		//all are 0
		if(!returnIndex.containsKey(mainClass)) {
			//the first return index
			returnIndex.put(mainClass, 0);
		}
	}
	
	private static ExecutableSequence current_sequence = null;
	
	public static ExecutableSequence currentSequence() {
		return current_sequence;
	}
	
	public static boolean hasMoreObject(Class<?> mainClass, Class<?>...classes) {
		init(mainClass, classes);
		List<ExecutableSequence> list = clazz_sequences.get(mainClass);
		if(list.isEmpty()) {
			return false;
		}
		int total_num = clazz_sequences.get(mainClass).size();
		int current_return_index = returnIndex.get(mainClass);
		return current_return_index >=0 && current_return_index < total_num;
	}
	
	/**
	 * This method creates a object o with type t
	 * */
	public static<T> T createNewObject(Class<T> mainClass, Class<?>...classes) {
		//init the pool
		init(mainClass, classes);
		
		Checker.checkTrue(clazz_sequences.containsKey(mainClass), "No sequences for main class: " + mainClass);
//		List<ExecutableSequence> list = clazz_sequences.get(mainClass);
//		if(list.isEmpty()) {
//			return null;
//		}
		if(!hasMoreObject(mainClass, classes)) {
			//System.out.println("Empty return in new object fuzzer.....");
			return null;
		}
		
		ExecutableSequence eSeq = nextSequence(mainClass);
		//System.out.println(eSeq.sequence.toCodeString());
		//ExecutableSequence eSeq = Randomness.randomMember(list);
		T retObj = null;
		ExecutionOutcome[] outcomes = eSeq.getAllResults();
		for(int i = outcomes.length - 1; i >=0; i--) {
			ExecutionOutcome outcome = outcomes[i];
			if(!(outcome instanceof NormalExecution)) {
				continue;
			} else {
				NormalExecution normalExec = (NormalExecution)outcome;
				Object runtimeObj = normalExec.getRuntimeValue();
				if(runtimeObj == null) {
					continue;
				} //else if(Reflection.canBeUsedAs(runtimeObj.getClass(), mainClass)) {
				else if(runtimeObj.getClass().equals(mainClass)) {
					retObj = (T) runtimeObj;
					break;
				}
			}
		}
		//System.out.println("ret object: " + retObj);
		return retObj;
	}
	
	public static<T> T createNewObject(Class<T> mainClass, Object ...args) {
		for(Object arg : args) {
			Checker.checkNull(arg, "Object argument should not be null.");
		}
		//start to create sequence
		
		return null;
	}
	
	public static void clear_state() {
		time_limit = 0;
		clazz_time_limit.clear();
		clazz_sequences.clear();
		returnIndex.clear();
		current_sequence = null;
	}
}